package com.chen.base.module.comment;

import java.math.BigDecimal;
import java.util.Stack;

/**
 * @Title: manager-spring-mvc-hb
 * @Description: to do
 * @Author: chenjiwei
 * @Date: 2021-01-02 6:06
 * @Version: 1.0.0
 */
public class AdvancedCalculator {
    // 计算值栈
    Stack<BigDecimal> valStack = new Stack<>();
    // 运算符栈
    Stack<OperatorConfig> operatorStack = new Stack<>();

    public BigDecimal execute(String exp) throws Exception {
        //2*(9-(1+4)*8)
        int index = 0;
        for(int i = 0; i < exp.length(); i++){
            char c = exp.charAt(i);
            if(isOperator(c)){
                if('(' == c) {
                    int lastIndex;
                    // 如果中间不存在‘(’，则直接计算括号内的表达式
                    // 如果中间存在‘(’，则获取最尾部的括号下标，并截取表达式递归重复此过程
                    if(exp.indexOf("(", i+1) > (lastIndex = exp.indexOf(")", i+1)) || exp.indexOf("(", i+1) == -1) {
                        if (lastIndex == -1) {
                            throw new Exception("表达式错误：" + exp);
                        }
                        valStack.push(new AdvancedCalculator().execute(exp.substring(i + 1, lastIndex)));
                        index = (i = lastIndex) + 1;
                    }else {
                        lastIndex = getIndexOfEndBracket(exp, i + 1, i + 1);
                        valStack.push(new AdvancedCalculator().execute(exp.substring(i + 1, lastIndex)));
                        index = (i = lastIndex) + 1;
                    }
                }else if(priorityCalculation(index == i ? valStack.pop() : new BigDecimal(exp.substring(index, i)), OperatorConfig.getByTypeid(c))) {
                    // 计算规则：同优先级左边先计算，高优先级先计算（乘除法优先于加减），
                    index = i + 1;
                }else {
                    // 当前字符c为运算符，并且未达到计算要求，那么此时的计算值和运算符将被压入堆栈中
                    valStack.push(new BigDecimal(exp.substring(index, i)));
                    operatorStack.push(OperatorConfig.getByTypeid(c));
                    index = i + 1;
                }
            }
        }
        if(index < exp.length()) valStack.push(new BigDecimal(exp.substring(index)));

        clearOperator();
        //2*(9*(1+4)-8)


        return valStack.pop();
    }

    /**
     * @Author chenjiwei
     * @Description 获取最右边的括号的下标
     * @Date 2021/1/2 5:53
     * @Param
     * @return
     */
    private int getIndexOfEndBracket(String exp, int indexL, int indexR) throws Exception {
        String sub = exp.substring(indexL, indexR);
        if(exp.indexOf("(", indexL) > exp.indexOf(")", indexR) || exp.indexOf("(", indexL) == -1) {
            if (exp.indexOf(")", indexR) == -1) {
                throw new Exception("表达式错误：" + exp);
            }
            return exp.indexOf(")", indexR);
        }
        return getIndexOfEndBracket(exp, exp.indexOf("(", indexL)+1, exp.indexOf(")", indexR)+1);
    }

    /**
     * @Author chenjiwei
     * @Description 优先计算，规则：同优先级左边先计算，高优先级先计算（乘除法优先于加减），
     * @Date 2021/1/2 5:48
     * @Param
     * @return
     */
    private boolean priorityCalculation(BigDecimal arg2, OperatorConfig operatorSign) throws Exception {
        if(operatorStack.size() > 0){
            OperatorConfig preOperatorSign = operatorStack.peek();
            if(isOperator(preOperatorSign.getTypeid()) && preOperatorSign.isMaxAndEqueal(operatorSign)){
                operatorStack.pop();
                valStack.push(preOperatorSign.calculate(valStack.pop(), arg2));
                operatorStack.push(operatorSign);
                return true;
            }
        }
        return false;
    }

    /**
     * @Author chenjiwei
     * @Description 将剩余的运算符进行计算，直到计算完为止
     * @Date 2021/1/2 5:51
     * @Param
     * @return
     */
    private void clearOperator() throws Exception {
        while (!operatorStack.empty()){
            OperatorConfig operatorSign = operatorStack.pop();
            BigDecimal arg2 = valStack.pop();
            valStack.push(operatorSign.calculate(valStack.pop(), arg2));
        }
    }

    /**
     * @Author chenjiwei
     * @Description
     * @Date 2021/1/2 5:52
     * @Param
     * @return
     */
    private boolean isOperator(char c){
        return '+' == c || '-' == c || '/' == c || '*' == c || '(' == c || ')' == c;
    }
}
